<?php
/**
 * php GEM Project
 * 
 * LICENSE
 * 
 * PHP System Architecture Enabling Rapid Application Deployment and Scalability
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @copyright Copyright (C) 2010 Zeletron LLC (http://zeletron.com)
 * @license http://gem.zeletron.com/license
 */

/*
 * GEM/Model/Mapper/Mapper.php
 * 
 * 
 * @author David Joly
 * @link http://zeletron.com
 * 
 * @category GEM
 * @package GEM_Model
 * @subpackage GEM_Model_Mapper 
 */

abstract class GEM_Model_Mapper_Mapper
{	
	/**
	 * Common select statement:
	 * 		>> SELECT * FROM <table> WHERE id = :id
	 * 
	 * @var PDOStatement
	 */
	protected $_selectStm;
	
	/**
	 * Common select statement:
	 * 		>> SELECT * FROM <table>
	 * 
	 * @var PDOStatement
	 */
	protected $_selectAllStm;
	
	/**
	 * Common delete statement:
	 * 		>> DELETE FROM <table> WHERE id = :id
	 * 
	 * @var PDOStatement
	 */
	protected $_deleteStm;
	
	/**
	 * PDO Object
	 * 
	 * @var PDO
	 */
	protected $_db;
	
	/**
	 * Constructor
	 * 
	 * @throws GEM_Model_Mapper_Exception
	 * @return void
	 */
	public function __construct()
	{
		$db = Zend_Registry::get('pdo');
		if(!($db instanceof PDO))
			throw new GEM_Model_Mapper_Exception('GEM_Model_Mapper_Mapper expects a PDO object to exist in the Registry.');
		$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
		$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
		$this->_db = $db;
			
			//Initialize prepared statements
		$this->_selectStm = $this->_db->prepare($this->_selectStm());
		$this->_selectAllStm = $this->_db->prepare($this->_selectAllStm());
		$this->_deleteStm = $this->_db->prepare("DELETE FROM {$this->_getTable()} WHERE id = :id");	
	}
	
	/**
	 * Override by extending mappers if needed.
	 * 
	 * @return string
	 */
	protected function _selectStm()
	{
		return "SELECT * FROM {$this->_getTable()} WHERE id = :id";
	}
	
	/**
	 * Override by extending mappers if needed.
	 * 
	 * @return string
	 */
	protected function _selectAllStm()
	{
		return "SELECT * FROM {$this->_getTable()}";
	}
	
	/**
	 * Returns and instance of GEM_Model_Domain_Object of
	 * the targetClass type. 
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @param int $id
	 * @return GEM_Model_Domain_Object
	 */
	public function getFromObjectManager($id)
	{
		return GEM_Model_Domain_ObjectManager::exists("GEM_Model_Domain_{$this->_getClassType()}", $id);
	}
	
	/**
	 * Registers an object with the object manager.
	 * 
	 * @uses GEM_Model_Domain_ObjectManager
	 * @param GEM_Model_Domain_Object $obj
	 * @return void
	 */
	public function addToObjectManager(GEM_Model_Domain_Object $obj)
	{
		GEM_Model_Domain_ObjectManager::register($obj);
	}
	
	/**
	 * Default find by id method. Additional finder methods should
	 * be provided by extending mapper classes.
	 * 
	 * @param int $id
	 * @return GEM_Model_Domain_Object
	 * @throws GEM_Model_Mapper_Exception
	 */
	public function find($id)
	{
		if(!is_int($id))
			throw new GEM_Model_Mapper_Exception('Find method expects an integer argument.');
		$existingObj = $this->getFromObjectManager($id);
		
		if($existingObj) 
			return $existingObj;
		//Retrieve object from database
		$this->_selectStm->execute(array(':id' => $id));
		$fields = $this->_selectStm->fetch();
		$this->_selectStm->closeCursor();
		if(!is_array($fields) || !isset($fields['id']))
			return null;
		$obj = $this->createObject($fields);
		return $obj;
	}
	
	/**
	 * Retrieves all objects of the extending mapper's
	 * class type. Extending mappers should provide a suite
	 * of their own specialized "find all" methods to more 
	 * narrowly define resultsets.
	 * 
	 * @return GEM_Model_Mapper_Collection
	 */
	public function findAll()
	{
		return $this->getCollection($this->_selectAllStm);
	}
	
	/**
	 * @param PDOStatement $selectStm The select statement defining the collection
	 * of objects that should be retrieved.
	 * @param array $stmValues | optional Array of named paramter and value pairs.
	 * @return GEM_Model_Mapper_Collection
	 * @throws GEM_Model_Mapper_Exception
	 */
	public function getCollection(PDOStatement $selectStm, array $stmValues = null)
	{
		return new GEM_Model_Mapper_Collection($this,$selectStm,$stmValues);
	}
	/**
	 * The createObject method is a wrapper for the concrete
	 * method _doCreateObject of the extending child class.
	 * 
	 * @param array $fields 
	 * @return GEM_Model_Domain_Object
	 */
	public function createObject(array $fields)
	{
		return $this->_doCreateObject($fields);
	}
	
	/**
	 * The insert method is a wrapper for the concrete
	 * method _doInsert of the extending child class.
	 * 
	 * @param GEM_Model_Domain_Object $obj
	 * @return void
	 */
	public function insert(GEM_Model_Domain_Object $obj)
	{
		$this->_doInsert($obj);
	}
	
	/**
	 * The delete method is a wrapper for the concrete
	 * method _doDelete of the extending child class.
	 * 
	 * @param GEM_Model_Domain_Object $obj
	 * @return void
	 */
	public function delete(GEM_Model_Domain_Object $obj)
	{
		$this->_doDelete($obj);
	}
	
	/**
	 * Updates a user's database record. Field values returned 
	 * from object methods will be serialized if they return
	 * either an object or an array except in the following case:
	 * 
	 *  	If the object is a child of GEM_Model_Domain_Object.
	 * 		In this case, the object will have a unique id. This id
	 * 		will become the field's value.
	 * 
	 * @param GEM_Model_Domain $obj
	 * @return void
	 */
	public function update(GEM_Model_Domain_Object $obj)
	{
		//Build SQL Statment
		$sql = "UPDATE {$this->_getTable()} SET ";
		$count = 0;
		foreach($obj->getChangedProperties() as $field){
			$sql .= 0 < $count++ ? ', ' : ''; 
			$sql .= "$field = :$field";
		}
		$sql .= " WHERE id = :id";
		//Using prepared statement to ensure proper escaping.
		//@see PDO Documentation
		$updateStm = $this->_db->prepare($sql);
		foreach($obj->getChangedProperties() as $field){
			$method = "get" . ucfirst($field);
			$value = $obj->$method();
			if($value instanceof GEM_Model_Domain_Object){
				//If the value is a GEM_Model_Domain_Object, the field value will be the id
				$value = $value->getId();
			} else if(is_array($value) || is_object($value)) {
				//If the field value is an array or an object that is not a child
				//of GEM_Model_Domain_Object, serialize the value.
				$value = serialize($value);
			} 
			$updateStm->bindValue(":$field",$value);
		}
		$updateStm->bindValue(':id',$obj->getId());
		$updateStm->execute();
	}
	
	/**
	 * @param GEM_Model_Domain_Object $obj
	 * @return void
	 */
	protected abstract function _doInsert(GEM_Model_Domain_Object $obj);
	
	/**
	 * Returns an instance of GEM_Model_Domain_Object
	 * 
	 * @param GEM_Model_Domain_Object $obj
	 * @return GEM_Model_Domain_Object
	 */
	protected abstract function _doCreateObject(array $fields);
	
	/**
	 * @param GEM_Model_Domain_Object $obj
	 * @return void
	 */
	protected abstract function _doDelete(GEM_Model_Domain_Object $obj);
	
	/**
	 * Returns the extending child class type.
	 * 
	 * @return string
	 */
	protected abstract function _getClassType();
	
	/**
	 * Returns the database table associated with the extending
	 * mapper class. This method must be provided by all extending
	 * mapper classes!
	 * 
	 * @return string
	 */
	protected abstract function _getTable();
	
}